已有不少小伙伴给出了Ueditor中拓展支持插入音频功能的方法,但还是存在一些特例性的问题。踩完数个坑后总算把音频功能基本搞顺畅,这里整理汇总留个记录,以防年长健忘。
1. 修改ueditor.config.js文件,增加插入音频功能入口:(1)在toolbars中增加'insertaudio'
toolbars: [['source', '|', 'undo', 'redo', '|',...'simpleupload', 'insertimage', 'emotion', 'insertaudio', 'insertvideo', '|','horizontal', 'date', 'time', 'spechars', '|', 'wordimage']](2)在labelMap中增加'insertaudio'对应的提示文字
labelMap: {'anchor' : '', 'vaecolor' : '自定义字体颜色','insertaudio' : '音频',}2. 修改ueditor.all.js文件,增加插入音频页面和命令入口:(1)在iframeUrlMap中增加插入音频页面的路径
var iframeUrlMap = {...'insertaudio':'~/dialogs/audio/audio.html','insertvideo':'~/dialogs/video/video.html',...};(2)在btnCmds中增加点击插入音频触发的命令
var btnCmds = ['undo', 'redo', 'formatmatch',...'insertaudio'];(3)在dialogBtns的ok属性中增加插入音频对话框
var dialogBtns = {noOk: ['searchreplace', 'help', 'spechars', 'webapp','preview'],ok: ['attachment', 'anchor', 'link', 'insertimage', 'map', 'gmap', ...'insertaudio', 'vaecolor']};3. 增加audio.html:(1)在ueditor/dialogs目录下增加audio目录,并增加audio.html,实现点击插入音频按钮时弹出的插入音频页面(此处可根据自身业务需求参照video.html或image.html来实现)
注:此页面中提供了插入音频和上传音频两种方式,现只实现了上传音频的功能。插入音频相对比较简单,不详述
音频对话框 px px px px 标题0%(2)增加audio.css用以实现audio.html中的相关样式(此处可照搬video.css或image.css后对应修改下)
/* 上传音频 */.tabbody #upload.panel {width: 0;height: 0;overflow: hidden;position: absolute !important;clip: rect(1px, 1px, 1px, 1px);background: #fff;display: block;}.tabbody #upload.panel.focus {width: 100%;height: 300px;display: block;clip: auto;}#upload .titleBar {width: 100%;margin: 10px;font-size: 14px;}#upload .titleBar .uploadAudioTitle {margin-left: 5px;width: 88%;}// 后面省略,照搬后修改即可...(3)增加audio.js用以实现audio.html中的上传音频、插入音频代码等操作(同样的,照搬vedio.js或image.js后对应修改即可)
注:涉及到几个特例化的地方:①音频上传至服务器或云存储,改造过图片上传的应该驾轻就熟了;②因要求给插入的音频配上标题,此处上传为单个音频文件上传,故要禁用多选批量上传;③开始上传、暂停上传、取消上传的相应功能;④最终insertList的结构,要传递给ueditor.all.js中的insertaudio命令以向编辑器内插入音频控件代码
(function () {var insertaudio,uploadaudio;// 音频文件key前缀var keyPrefix = editor.getOpt('keyPrefix') + '/audio';window.onload = function () {initTabs();initButtons();};/* 初始化tab标签 */function initTabs() {var tabs = $G('tabhead').children;var audio = editor.selection.getRange().getClosedNode();var id = tabs[0].getAttribute('data-content-id');for (var i = 0; i < tabs.length; i++) {domUtils.on(tabs[i], "click", function (e) {var j, bodyId, target = e.target || e.srcElement;id = target.getAttribute('data-content-id');for (j = 0; j < tabs.length; j++) {bodyId = tabs[j].getAttribute('data-content-id');if(tabs[j] == target){domUtils.addClass(tabs[j], 'focus');domUtils.addClass($G(bodyId), 'focus');}else {domUtils.removeClasses(tabs[j], 'focus');domUtils.removeClasses($G(bodyId), 'focus');}}});}switch (id) {case 'remote': // 插入音频/远程音频(预留)insertaudio = insertaudio || new RemoteAudio();break;case 'upload': // 上传音频(主要)uploadaudio = uploadaudio || new UploadAudio('queueList');break;}}/* 初始化onok事件 */function initButtons() {dialog.onok = function () {var remote = false, list = [], id, tabs = $G('tabhead').children;for (var i = 0; i < tabs.length; i++) {if (domUtils.hasClass(tabs[i], 'focus')) {id = tabs[i].getAttribute('data-content-id');break;}}switch (id) {case 'remote':list = insertaudio.getInsertList();break;case 'upload':list = uploadaudio.getInsertList();var count = uploadaudio.getQueueCount();if (count) {$('.info', '#queueList').html('' + '还有2个未上传文件'.replace(/[\d]/, count) + '');return false;}// 配上标题var title = $('.uploadAudioTitle').val();if (!title || $.trim(title) == '') {alert('请填写标题');$('.uploadAudioTitle').focus();return false;}if(list) {for(var i = 0; i < list.length; i++) {var f = list[i];f['title'] = title;list[i] = f;}}break;}if(list) {editor.execCommand('insertaudio', list);remote && editor.fireEvent("catchRemoteAudio");}};}/* 上传音频 */function UploadAudio(target) {this.$wrap = target.constructor == String ? $('#' + target) : $(target);this.init();}UploadAudio.prototype = {init: function () {this.audioList = [];this.initContainer();this.initUploader();},initContainer: function () {this.$queue = this.$wrap.find('.filelist');},/* 初始化容器 */initUploader: function () {var _this = this,$ = jQuery,// just in case. Make sure it's not an other libaray.$wrap = _this.$wrap,// 文件容器$queue = $wrap.find('.filelist'),// 状态栏,包括进度和控制按钮$statusBar = $wrap.find('.statusBar'),// 文件总体选择信息。$info = $statusBar.find('.info'),// 上传按钮$upload = $wrap.find('.uploadBtn'),// 上传按钮$filePickerBtn = $wrap.find('.filePickerBtn'),// 上传按钮$filePickerBlock = $wrap.find('.filePickerBlock'),// 没选择文件之前的内容。$placeHolder = $wrap.find('.placeholder'),// 总体进度条$progress = $statusBar.find('.progress').hide(),// 添加的文件数量fileCount = 0,// 添加的文件总大小fileSize = 0,// 优化retina, 在retina下这个值是2ratio = window.devicePixelRatio || 1,// 缩略图大小thumbnailWidth = 550 * ratio,thumbnailHeight = 113 * ratio,// 可能有pedding, ready, uploading, confirm, done.state = '',// 所有文件的进度信息,key为file idpercentages = {},supportTransition = (function () {var s = document.createElement('p').style,r = 'transition' in s ||'WebkitTransition' in s ||'MozTransition' in s ||'msTransition' in s ||'OTransition' in s;s = null;return r;})(),// WebUploader实例uploader,actionUrl = editor.getActionUrl(editor.getOpt('audioActionName')),acceptExtensions = (editor.getOpt('audioAllowFiles') || []).join('').replace(/\./g, ',').replace(/^[,]/, ''),audioMaxSize = editor.getOpt('audioMaxSize'),imageCompressBorder = editor.getOpt('imageCompressBorder');if (!WebUploader.Uploader.support()) {$('#filePickerReady').after($('').html(lang.errorNotSupport)).hide();return;} else if (!editor.getOpt('audioActionName')) {$('#filePickerReady').after($('').html(lang.errorLoadConfig)).hide();return;}uploader = _this.uploader = WebUploader.create({pick: {id: '#filePickerReady',label: lang.uploadSelectFile,multiple: false // 限制为单选},accept: {title: 'Audios',extensions: acceptExtensions,mimeTypes: 'audio/mp3,audio/amr,audio/wma,audio/wav'},swf: '../../third-party/webuploader/Uploader.swf',server: actionUrl,fileVal: editor.getOpt('audioFieldName'),duplicate: false,fileNumLimit: 1,// 限制为单个文件fileSingleSizeLimit: audioMaxSize// 默认 30 M});uploader.addButton({id: '#filePickerBlock'});//uploader.addButton({//id: '#filePickerBtn',//label: lang.uploadAddFile//});setState('pedding');// 当有文件添加进来时执行,负责view的创建function addFile(file) {var $li = $('' +'' + file.name + '
' +'' +''),$btns = $('' +'' + lang.uploadDelete + '' +'' + lang.uploadTurnRight + '' +'' + lang.uploadTurnLeft + '').appendTo($li),$prgress = $li.find('p.progress span'),$wrap = $li.find('p.imgWrap'),$info = $('').hide().appendTo($li),showError = function (code) {switch (code) {case 'exceed_size':text = lang.errorExceedSize;break;case 'interrupt':text = lang.errorInterrupt;break;case 'http':text = lang.errorHttp;break;case 'not_allow_type':text = lang.errorFileType;break;default:text = lang.errorUploadRetry;break;}$info.text(text).show();};if (file.getStatus() === 'invalid') {showError(file.statusText);} else {percentages[ file.id ] = [ file.size, 0 ];file.rotation = 0;/* 检查文件格式 */if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {showError('not_allow_type');uploader.removeFile(file);}}file.on('statuschange', function (cur, prev) {if (prev === 'progress') {$prgress.hide().width(0);} else if (prev === 'queued') {$li.off('mouseenter mouseleave');$btns.remove();}// 成功if (cur === 'error' || cur === 'invalid') {showError(file.statusText);percentages[ file.id ][ 1 ] = 1;} else if (cur === 'interrupt') {showError('interrupt');} else if (cur === 'queued') {percentages[ file.id ][ 1 ] = 0;} else if (cur === 'progress') {$info.hide();$prgress.css('display', 'block');} else if (cur === 'complete') {}$li.removeClass('state-' + prev).addClass('state-' + cur);});$li.on('mouseenter', function () {$btns.stop().animate({height: 30});});$li.on('mouseleave', function () {$btns.stop().animate({height: 0});});$btns.on('click', 'span', function () {var index = $(this).index(),deg;switch (index) {case 0:uploader.removeFile(file);return;case 1:file.rotation += 90;break;case 2:file.rotation -= 90;break;}if (supportTransition) {deg = 'rotate(' + file.rotation + 'deg)';$wrap.css({'-webkit-transform': deg,'-mos-transform': deg,'-o-transform': deg,'transform': deg});} else {$wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');}});$li.insertBefore($filePickerBlock);// 隐藏继续添加控件,设置为单个文件上传$filePickerBlock.hide();}// 取消上传function cancelFile(file) { var $li = $('#' + file.id); var spans = $progress.children(); spans.eq(0).text('0%'); spans.eq(1).css('width', '0%'); $progress.css('display', 'none'); $('.statusBar').children('.info').css('display', 'inline-block'); $('.error').remove(); var upBtn = $('.uploadBtn'); upBtn.removeClass('state-paused disabled'); upBtn.addClass('state-ready'); upBtn.html(lang.uploadStart);}// 负责view的销毁function removeFile(file) {var $li = $('#' + file.id);delete percentages[ file.id ];updateTotalProgress();$li.off().find('.file-panel').off().end().remove();// 显示继续添加控件$filePickerBlock.show();}function updateTotalProgress() {var loaded = 0,total = 0,spans = $progress.children(),percent;$.each(percentages, function (k, v) {total += v[ 0 ];loaded += v[ 0 ] * v[ 1 ];});percent = total ? loaded / total : 0;spans.eq(0).text(Math.round(percent * 100) + '%');spans.eq(1).css('width', Math.round(percent * 100) + '%');updateStatus();}function setState(val, files) {if (val != state) {var stats = uploader.getStats();$upload.removeClass('state-' + state);$upload.addClass('state-' + val);switch (val) {/* 未选择文件 */case 'pedding':$queue.addClass('element-invisible');$statusBar.addClass('element-invisible');$placeHolder.removeClass('element-invisible');$progress.hide(); $info.hide();uploader.refresh();break;/* 可以开始上传 */case 'ready':$placeHolder.addClass('element-invisible');$queue.removeClass('element-invisible');$statusBar.removeClass('element-invisible');$progress.hide(); $info.show();$upload.text(lang.uploadStart);uploader.refresh();break;/* 上传中 */case 'uploading':$progress.show(); $info.hide();//$upload.text(lang.uploadPause);$upload.text(lang.uploadCancel);break;/* 暂停上传 */case 'paused':$progress.show(); $info.hide();$upload.text(lang.uploadContinue);break;/* 取消上传 */case 'cancel':$placeHolder.addClass('element-invisible');$queue.removeClass('element-invisible');$statusBar.removeClass('element-invisible');$progress.hide(); $info.show();$upload.text(lang.uploadStart);uploader.refresh();break;case 'confirm':$progress.show(); $info.hide();$upload.text(lang.uploadStart);stats = uploader.getStats();if (stats.successNum && !stats.uploadFailNum) {setState('finish');return;}break;case 'finish':$progress.hide(); $info.show();if (stats.uploadFailNum) {$upload.text(lang.uploadRetry);} else {$upload.text(lang.uploadStart);}break;}state = val;updateStatus();}if (!_this.getQueueCount()) {$upload.addClass('disabled')} else {$upload.removeClass('disabled')}}function updateStatus() {var text = '', stats;if (state === 'ready') {text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));} else if (state === 'confirm') {stats = uploader.getStats();if (stats.uploadFailNum) {text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);}} else {stats = uploader.getStats();text = lang.updateStatusFinish.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize)).replace('_', stats.successNum);if (stats.uploadFailNum) {text += lang.updateStatusError.replace('_', stats.uploadFailNum);}}$info.html(text);}uploader.on('fileQueued', function (file) {fileCount++;fileSize += file.size;if (fileCount === 1) {$placeHolder.addClass('element-invisible');$statusBar.show();}addFile(file);});uploader.on('fileDequeued', function (file) {fileCount--;fileSize -= file.size;removeFile(file);updateTotalProgress();});uploader.on('filesQueued', function (file) {if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {setState('ready');}updateTotalProgress();});uploader.on('all', function (type, files) {switch (type) {case 'uploadFinished':setState('confirm', files);break;case 'startUpload':/* 添加额外的GET参数 */var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '';//url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);uploader.option('server', editor.getOpt('imageUrl'));setState('uploading', files);break;case 'stopUpload':setState('paused', files);break;}});uploader.on('uploadBeforeSend', function (file, data, header) {//这里可以通过data对象添加POST参数header['X_Requested_With'] = 'XMLHttpRequest';// 上传tokenvar token = getUploadToken4UE();if (token == null) {alert('获取上传token异常,请稍后再试~');return false;}data['token'] = token;// 文件keydata['key'] = keyPrefix + '/' + uuid();});uploader.on('uploadProgress', function (file, percentage) {var $li = $('#' + file.id),$percent = $li.find('.progress span');$percent.css('width', percentage * 100 + '%');percentages[ file.id ][ 1 ] = percentage;updateTotalProgress();});uploader.on('uploadSuccess', function (file, ret) {var $file = $('#' + file.id);try {var responseText = (ret._raw || ret),json = utils.str2json(responseText);if (json.state == 'SUCCESS') {//_this.audioList.push(json);_this.audioList[$file.index()] = json;//按选择好的文件列表顺序存储$file.append('');} else {$file.find('.error').text(json.state).show();}} catch (e) {$file.find('.error').text(lang.errorServerUpload).show();}});uploader.on('uploadError', function (file, code) {});uploader.on('error', function (code, file) {if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {addFile(file);}});uploader.on('uploadComplete', function (file, ret) {});$upload.on('click', function () {if ($(this).hasClass('disabled')) {return false;}if (state === 'ready') {uploader.upload();} else if (state === 'paused') {uploader.upload();} else if (state === 'cancel') {uploader.upload();} else if (state === 'uploading') {// uploader.stop();// 调整为取消上传var file = uploader.getFiles()[0];uploader.stop(file);// removeFile(file);cancelFile(file);//setState('cancel');}});$upload.addClass('state-' + state);updateTotalProgress();},getQueueCount: function () {var file, i, status, readyFile = 0, files = this.uploader.getFiles();for (i = 0; file = files[i++]; ) {status = file.getStatus();if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;}return readyFile;},destroy: function () {this.$wrap.remove();},getInsertList: function () {var i, data, list = [],prefix = editor.getOpt('audioUrlPrefix');for (i = 0; i < this.audioList.length; i++) {data = this.audioList[i];if(data == undefined){continue;}//修改ENDlist.push({src: prefix + data.key,key: + new Date()// 以时间戳作为音频控件父div的id});}return list;}};})();(4)修改ueditor.css文件,增加插入音频按钮图标及页面窗口的相关样式:
.edui-default .edui-for-insertaudio .edui-icon {background-position: -320px -20px;}/*audio-dialog*/.edui-default .edui-for-insertaudio .edui-dialog-content {width: 590px;height: 390px;}/* audio*/.edui-default .edui-for-insertaudio .edui-icon {background-image: url(../images/audio.png) !important;}音频按钮图标放在ueditor目录的themes/default/images下。
(5)修改zh-cn.js文件,增加insertaudio的相关配置(类同insertvideo):
'insertaudio' : {'static' : {'lang_tab_remote' : "插入音频",'lang_tab_upload' : "上传音频",},'uploadSelectFile' : '点击选择音频文件','uploadAddFile' : '继续添加','uploadStart' : '开始上传','uploadPause' : '暂停上传','uploadContinue' : '继续上传','uploadCancel' : '取消上传','uploadRetry' : '重试上传','uploadDelete' : '删除','uploadTurnLeft' : '向左旋转','uploadTurnRight' : '向右旋转','uploadPreview' : '预览中','uploadNoPreview' : '不能预览','updateStatusReady' : '选中_个音频文件,共_KB。','updateStatusConfirm' : '已成功上传_个音频文件,_个音频文件上传失败','updateStatusFinish' : '共_个(_KB),_个上传成功','updateStatusError' : ',_个音频文件上传失败。','errorNotSupport' : 'WebUploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器。','errorLoadConfig' : '后端配置项没有正常加载,上传插件不能正常使用!','errorExceedSize' : '文件大小超出','errorFileType' : '文件格式不允许','errorInterrupt' : '文件传输中断','errorUploadRetry' : '上传失败,请重试','errorHttp' : 'http请求错误','errorServerUpload' : '服务器返回出错','remoteLockError' : "宽高不正确,不能所定比例",'numError' : "请输入正确的长度或者宽度值!例如:123,400",'audioUrlError' : "不允许的音频格式或者图片域!",'audioLoadError' : "音频加载失败!请检查